#include <cassert>
#include <climits>
#include <cmath>

#include <opencv2/opencv.hpp>

#include "SkinDetector.h"


SkinDetector::SkinDetector() {
    
}

SkinDetector::~SkinDetector() {
    
}

void SkinDetector::detect(const cv::Mat & image, cv::Mat & result,
                          SkinDetectionMethod method) {
    
	assert(image.rows == result.rows);
	assert(result.depth() == CV_8UC1);
    
	switch (method) {
        case HSV_DETECTION:
            detectSkinFromHsvColorSpace(image, result);
            break;
        case RGB_DETECTION:
            detectSkinFromRgbColorSpace(image, result);
            break;
        default:
            break;
	}
}

void SkinDetector::detectSkinFromHsvColorSpace(const cv::Mat & image,
                                               cv::Mat & result) {
	int row, col;
	unsigned char hue, sat;
    
	for (row = 0; row < image.rows; row++) {
		for (col = 0; col < image.cols; col++) {
			hue = image.at<cv::Vec3b>(row, col)[0];
			sat = image.at<cv::Vec3b>(row, col)[1];
            
			if (isSkin(hue, sat)) {
				result.at<unsigned char>(row, col) = UCHAR_MAX;
			} else {
				result.at<unsigned char>(row, col) = 0;
			}
		}
	}
}

void SkinDetector::detectSkinFromRgbColorSpace(const cv::Mat & image,
                                               cv::Mat & result) {
	int row, col;
	unsigned char red, green, blue;
    
	for (row = 0; row < image.rows; row++) {
		for (col = 0; col < image.cols; col++) {
			red = image.at<cv::Vec3b>(row, col)[0];
			green = image.at<cv::Vec3b>(row, col)[1];
			blue = image.at<cv::Vec3b>(row, col)[2];
            
			if (isSkin(red, green, blue)) {
				result.at<unsigned char>(row, col) = UCHAR_MAX;
			} else {
				result.at<unsigned char>(row, col) = 0;
			}
		}
	}
}

bool SkinDetector::isSkin(unsigned char hue, unsigned char sat) {
    
	return (hue < 36 / 2 && sat > 0.1 * 255 && sat < 0.57 * 255);
}

bool SkinDetector::isSkin(unsigned char red, unsigned char green,
                          unsigned char blue) {
	double r, g;
	double g1, g2, w;
	unsigned int sum;
    
	sum = red + green + blue;
    
	if (sum == 0)
		return false;
    
	r = (double) red / (double) sum;
	g = (double) green / (double) sum;
    
	g1 = -0.7279 * pow(r, 2.0) + 0.6066 * r + 0.1766;
	g2 = -1.8423 * pow(r, 2.0) + 1.5294 * r + 0.0422;
    
	w = sqrt(pow(r - 0.33, 2.0) + pow(g - 0.33, 2.0));
    
	return (g1 < g && g < g2 && w > 0.02);
}
